#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <cstring>
#include <map>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <iostream>
#include <chrono>
#include <future>
#include "../Negotiation/ECDHMethods.h"
#include "../Negotiation/OutputMethods.h"
#include "../Negotiation/RandomMethods.h"
#include "../Negotiation/HashMethods.h"
#include "../Utils/ConvertUtil.h"
#include "../Utils/JsonUtil.h"
#include "../Buffer/InfoBuffer.h"
#include "../CallBacker/Encrypt/3DES.h"
#include "../Netfilter/NetfilterA.h"

#define PORT 3490
#define BACKLOG 10
#define ECDH_SIZE 33
#define MAXDATASIZE 1000
using namespace std;

class SendingMsgDealer{
protected:
	SendingMsgDealer()=default;
	
public:
	SendingMsgDealer(const string &h);
	static pthread_mutex_t mutex;
    	static SendingMsgDealer* get_instance(const string &h);
	string oneRTTNegotiate(const string &_message);
	string zeroRTTNegotiate(const string &_appData);
	string encryptNegotiate(map<string,string> param);
	string receiveMsg();
	void encrypt();
	string hash(map<string,string> param);
	void sendMsg(const string s);
private:
	string host;
	int sockfd;
	int numbytes;
	struct hostent *he;
	struct sockaddr_in their_addr;
	static SendingMsgDealer *sender;
	void connect_receiver();
	void initialize();
};
//making this object singleton
pthread_mutex_t SendingMsgDealer::mutex;
SendingMsgDealer* SendingMsgDealer::sender = NULL;

SendingMsgDealer:: SendingMsgDealer(const string &h):host(h){
	pthread_mutex_init(&mutex,NULL);
	this->initialize();
}

SendingMsgDealer* SendingMsgDealer:: get_instance(const string &h)
{
	if(sender==NULL)
	{
		 pthread_mutex_lock(&mutex);
      		  if (sender == NULL)
            			sender= new SendingMsgDealer(h);
       		 pthread_mutex_unlock(&mutex);
	}
	return sender;
}


void SendingMsgDealer::connect_receiver(){
	if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr))==-1){
		perror("connect error");
		exit(1);
	}
}
void SendingMsgDealer::initialize(){
	if((he=gethostbyname(host.c_str()))==NULL){
		herror("get host by name");
		exit(1);
	}
	if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
		perror("socket error");
		exit(1);
	}
	this->their_addr.sin_family=AF_INET;
	this->their_addr.sin_port=htons(PORT);
	this->their_addr.sin_addr=*((struct in_addr *)he->h_addr);
	bzero(&(their_addr.sin_zero),sizeof(their_addr.sin_zero));
	connect_receiver();

}
void  SendingMsgDealer::sendMsg(const string s){
	if(!fork()){
		if(send(sockfd,s.c_str(),s.size(),0)==-1)
			perror("send error");
		close(sockfd);
		exit(0);
	}
}

string SendingMsgDealer::receiveMsg(){
	char buf[MAXDATASIZE];
	if((numbytes=recv(sockfd,buf,MAXDATASIZE,0))==-1){
		perror("recv");
		exit(1);
	}
	buf[numbytes]='\0';
	string str=ConvertUtil::c_to_string(buf,numbytes);
	return str;
}

string SendingMsgDealer::oneRTTNegotiate(const string &_message)
{
	//initialize
	ECDHMethods ecdhMethods;
	OutputMethods output;
	HashMethods hash;
	RandomMethods rand;
	//ecdh key exchange
	unsigned char *keydata = (unsigned char *)malloc(ECDH_SIZE);
	EC_KEY *ecdh = ecdhMethods.genECDHtemppubkey(keydata);
	string pubK=ConvertUtil::uc_to_string(keydata,ECDH_SIZE);
	sendMsg(pubK);
	string pubK2=receiveMsg();
	unsigned char *keydata2=(unsigned char *)pubK2.c_str();
	unsigned char *ECDH_keydata2 =ecdhMethods.genECDHsharedsecret(ecdh, keydata2, ECDH_SIZE-1);
	string key=ConvertUtil::uc_to_string(ECDH_keydata2,ECDH_SIZE-1);
	string keyBase64=output.Base64Encode(key.c_str(),key.size(), true);
	//psk hashing authentication
	string clientRandom=rand.random();
	sendMsg(clientRandom);
	string json_clipers=receiveMsg();
	map<string,string> json_map=JsonUtil::json_to_map(json_clipers);
	string severRandom=json_map["server_random"];
	string mac=json_map["mac"];
	string clipers=keyBase64+clientRandom+severRandom;
	string recvMac=hash.getMD5HashVal(clipers.c_str());
	InfoBuffer *buffer=InfoBuffer::get_instance();
	buffer->putEncryptKey(key);
	buffer=NULL;
	//cout<<keyBase64<<endl;
	if(recvMac==mac)
		return "one rtt negotiation ok key:"+keyBase64;
	else
		return "authentication failed";
}

string SendingMsgDealer::zeroRTTNegotiate(const string &_appData)
{
	//initialize
	ECDHMethods ecdhMethods;
	OutputMethods output;
	HashMethods hash;
	RandomMethods rand;
	//generate SS key
	string pub_sever_static_str=receiveMsg();
	unsigned char *pub_sever_static=(unsigned char *)pub_sever_static_str.c_str();
	unsigned char *pub_client = (unsigned char *)malloc(ECDH_SIZE);
	EC_KEY *pri_client = ecdhMethods.genECDHtemppubkey(pub_client);
	unsigned char *SS =ecdhMethods.genECDHsharedsecret(pri_client, pub_sever_static, ECDH_SIZE-1);
	string SS_str=ConvertUtil::uc_to_string(SS,ECDH_SIZE-1);
	string SS_base64=output.Base64Encode(SS_str.c_str(),SS_str.size(),true);
	string pub_client_str=ConvertUtil::uc_to_string(pub_client,ECDH_SIZE);
	//send client_random,app_data and pub_client
	map<string,string> clipers_map;
	string clientRandom=rand.random();
	clipers_map.insert(make_pair("appData",_appData));
	clipers_map.insert(make_pair("client_random",clientRandom));
	clipers_map.insert(make_pair("pub_client",pub_client_str));
	string clipers=JsonUtil::map_to_json(clipers_map);
	sendMsg(clipers);
	std::chrono::milliseconds dur(10);
              std::this_thread::sleep_for(dur);
	sendMsg(pub_client_str);
	cout<<"SS_base64:"<<SS_base64<<endl;
	//verfying
	string json_clipers=receiveMsg();
	
	map<string,string> mac_map=JsonUtil::json_to_map(json_clipers);
	string severRandom=mac_map["sever_random"];
	//string pub_sever_str=mac_map["pub_sever"];
	string recvMac=mac_map["mac"];
	std::chrono::milliseconds dur1(10);
              std::this_thread::sleep_for(dur1);
	string pub_sever_str=receiveMsg();
	string hash_clipers=clientRandom+severRandom+pub_sever_str;
	string mac=hash.getMD5HashVal(hash_clipers.c_str());
	string message;
	//generate ES key
	unsigned char* pub_sever= (unsigned char *)malloc(ECDH_SIZE);
	pub_sever=(unsigned char *)pub_sever_str.c_str();
	unsigned char *ES =ecdhMethods.genECDHsharedsecret(pri_client, pub_sever, ECDH_SIZE-1);
	string ES_str=ConvertUtil::uc_to_string(ES,ECDH_SIZE-1);
	string ES_base64=output.Base64Encode(ES_str.c_str(),ES_str.size(),true);
	cout<<"ES_base64:"<<ES_base64<<endl;
	InfoBuffer *buffer=InfoBuffer::get_instance();
	buffer->putEncryptKey(ES_base64);
	buffer=NULL;

	if(mac==recvMac)
		message=message+"zero rtt authentication success,key: "+ES_base64;
	else
		message="zero rtt authentication failed";
	return message;
}

string SendingMsgDealer:: encryptNegotiate(map<string,string> param)
{
	string clipers=JsonUtil::map_to_json(param);
	sendMsg(clipers);
	string result=receiveMsg();
	string message="encryptNegotiate ok "+result;
	return message;
}

void SendingMsgDealer::encrypt()
{
	OutputMethods output;
	string test_str="this is the tester";
	int len=test_str.length();
	string len_str=to_string(len) ;
	sendMsg(len_str);
	InfoBuffer *buffer=InfoBuffer::get_instance();
	string key=buffer-> getEncryptKey();
	string key24="1234567890";
	if(key!="")
		key24=key.substr(0,24);
	TDES des;
	string e_cry=des.TDES_encrypt(test_str,key24);
	//string e_cry64=output.Base64Encode(e_cry.c_str(),e_cry.size(),true);
	string ok=receiveMsg();
	//cout<<"key24:"<<key24<<endl;
	sendMsg(e_cry);
}

string SendingMsgDealer::hash(map<string,string> param)
{
	NetfilterA *filter=new NetfilterA("hash",param);
	string result=filter->filter_start();
	delete filter;
	return result;
}

